﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.DataSetGenerator;
using System.IO;
using System.Xml.Serialization;
using System.Reflection;
using System.Data.SqlClient;
using System.Data;
using System.Xml;

namespace DataSetGen
{
    class Program
    {

        internal static ProgramParameters Parameters { get; private set; }

        static void Main(string[] args)
        {
            try
            {
                // Check parameters
                if (args == null || args.Length == 0)
                {
                    PrintLogo();
                    PrintHelp();
                    return;
                }

                // Process parameters
                Parameters = ProcessParameters(args);

                if (!Parameters.NoLogo)
                {
                    PrintLogo();
                }

                if (Parameters.ShowHelp)
                {
                    PrintHelp();
                }
                else if (Parameters.ShowSample)
                {
                    ShowSampleConfig();
                }
                else if (Parameters.Reverse)
                {
                    Reverse(Parameters.XsdFileName);
                }
                else if (Parameters.Config)
                {
                    ProcessConfig(Parameters.ConfigFileName, Parameters);
                }

                Environment.ExitCode = 0;
            }
            catch (Exception ex)
            {
                ProcessException(ex);
#if DEBUG
                ConsoleUtil.PrintMessage("DEBUG mode - press any key to exit", System.Diagnostics.TraceLevel.Info);
                Console.ReadKey();
#endif
                Environment.ExitCode = 1;
            }
        }


        /// <summary>
        /// Splits the given argument using colon as the separator char,
        /// cuts of the first argument's leading slash and returns
        /// an array which containins key and value pair.
        /// </summary>
        /// <param name="argument"></param>
        /// <returns></returns>
        private static String[] ProcessArgument(String argument)
        {
            if (argument == null ||
                !argument.StartsWith("/"))
                throw new ArgumentException(String.Format("Invalid argument: {0}", argument), argument);

            String[] result = new String[2] {"", ""};

            if (!argument.Contains(':'))
            {
                result[0] = argument.TrimStart('/');
            }
            else
            {
                String[] _result = argument.Split(new char[]{':'}, 2);
                if (_result.Length > 0)
                {
                    String firstPart = _result[0];
                    firstPart = firstPart.TrimStart('/');
                    result[0] = firstPart;
                    if (_result.Length > 1)
                        result[1] = _result[1];
                }
            }
            return result;
        }

        private static ProgramParameters ProcessParameters(String[] args)
        {
            List<String> largs = new List<string>();
            largs.AddRange(args);

            ProgramParameters parameters = new ProgramParameters();

            foreach (String arg in largs)
            {
                String[] parg = ProcessArgument(arg);

                switch (parg[0].ToLower())
                {
                    case "help":
                    case "h":
                    case "?":
                        parameters.ShowHelp = true;
                        break;

                    case "config":
                    case "c":
                        parameters.Config = true;
                        parameters.ConfigFileName = parg[1];
                        break;

                    case "validate":
                    case "v":
                        parameters.Validate = true;
                        parameters.ConfigFileName = parg[1];
                        break;

                    case "reverse":
                    case "r":
                        parameters.Reverse = true;
                        parameters.XsdFileName = parg[1];
                        break;

                    case "connectionstring":
                    case "cs":
                        parameters.ConnectionString = parg[1];
                        break;

                    case "dataset":
                    case "d":
                        parameters.DataSetsToInclude.Add(parg[1]);
                        break;

                    case "adapters":
                    case "a":
                        parameters.GenerateDataAdapters = Util.StringToBool(parg[1]);
                        break;

                    case "verbose":
                        parameters.Verbose = true;
                        break;

                    case "nologo":
                        parameters.NoLogo = true;
                        break;

                    case "we":
                        parameters.TreatWarningsAsErrors = Util.StringToBool(parg[1]);
                        break;

                }
            }

            return parameters;
        }


        /// <summary>
        /// Processes the given exception by printing an error message and a stack trace.
        /// </summary>
        /// <param name="ex"></param>
        private static void ProcessException(Exception ex)
        {
            String message = ex.Message;
            StringBuilder userMessage = new StringBuilder();

            if (ex is InvalidOperationException)
            {
                Exception iex = ex;
                Exception iexchild = ex.InnerException;
                while (iexchild != null)
                {
                    userMessage.Append(iexchild.Message);
                    iex = iexchild;
                    iexchild = iex.InnerException;
                }
            }

            ConsoleUtil.PrintMessage(message, System.Diagnostics.TraceLevel.Error);
            ConsoleUtil.PrintMessage(userMessage.ToString(), System.Diagnostics.TraceLevel.Error);
            ConsoleUtil.PrintMessage(ex.StackTrace, System.Diagnostics.TraceLevel.Verbose);

            Environment.ExitCode = 1;
        }

        /// <summary>
        /// Displays the help message
        /// </summary>
        private static void PrintHelp()
        {
            Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("DataSetGen.Help.txt");
            if (stream != null)
            {
                using (StreamReader rdr = new StreamReader(stream))
                {
                    ConsoleUtil.PrintMessage(rdr.ReadToEnd(), System.Diagnostics.TraceLevel.Off);
                }
            }
        }

        private static void PrintLogo()
        {
            Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("DataSetGen.Logo.txt");
            if (stream != null)
            {
                using (StreamReader rdr = new StreamReader(stream))
                {
                    String logo = rdr.ReadToEnd();
                    logo = String.Format(logo, Assembly.GetExecutingAssembly().GetName().Version);
                    ConsoleUtil.PrintMessage(logo, System.Diagnostics.TraceLevel.Off);
                }
            }
        }

        /// <summary>
        /// Processes the given DataSet definition file, transforms it to an 
        /// instanse of the DataSetDescriptor object and prints its Xml
        /// </summary>
        /// <param name="pathToXsd">The path of the file that contains the DataSet definition.</param>
        private static void Reverse(String pathToXsd)
        {
            try
            {
                FileUtil.CheckFileExists(pathToXsd);

                DataSet dataSet = new DataSet();
                dataSet.ReadXml(pathToXsd);

                var descriptor = SqlDataSetReverseEngineer.Process(dataSet);
                XmlSerializer ser = new XmlSerializer(typeof(DataSetDescriptor));
                ser.Serialize(Console.Out, descriptor);

            }
            catch (Exception ex)
            {
                ProcessException(ex);
            }
        }

        /// <summary>
        /// Validates the given configuration file
        /// </summary>
        /// <param name="pathToConfig"></param>
        private static void Validate(String pathToConfig)
        {
            FileUtil.CheckFileExists(pathToConfig);

            TypedDataSetGeneratorConfiguration config = TypedDataSetGeneratorConfiguration.Load(pathToConfig);

            ConsoleUtil.PrintMessage("File is valid", System.Diagnostics.TraceLevel.Info);
        }

        private static void ProcessConfig(String pathToConfig, ProgramParameters options)
        {
            if ("sample".Equals(pathToConfig))
            {
                ShowSampleConfig();
            }
            else
            {
                FileUtil.CheckFileExists(pathToConfig);

                TypedDataSetGeneratorConfiguration config = TypedDataSetGeneratorConfiguration.Load(pathToConfig);
                if (!String.IsNullOrEmpty(options.ConnectionString))
                    config.ConnectionString = options.ConnectionString;

                if (options.GenerateDataAdapters.HasValue)
                {
                    config.DataAdapters.GenerateDataAdapters = options.GenerateDataAdapters.Value;
                }

                if (options.DataSetsToInclude.Count != 0)
                {
                    config.DataSetsToInclude.AddRange(options.DataSetsToInclude); 
                }

                SqlGeneratorFacade facade = new SqlGeneratorFacade(config);
                facade.Message += new MessageEventHandler(On_Message);

                facade.Run();
            }
        }

        static void On_Message(object sender, MessageEventArgs eargs)
        {
            ConsoleUtil.PrintMessage(eargs.Message, eargs.TraceLevel);
        }

        private static void ShowSampleConfig()
        {
            XmlDocument schema = Util.GetConfigXsdSchema(1, 0);
            schema.Save(Console.Out);
        }
    }
}
